package comg.springboot.log.demo.log;

import comg.springboot.log.demo.annotation.SysControllerLog;
import comg.springboot.log.demo.pojo.Log;
import comg.springboot.log.demo.pojo.User;
import comg.springboot.log.demo.service.LogService;
import comg.springboot.log.demo.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @Author: jxm
 * @Description:
 * @Date: 2022/4/15 15:05
 * @Version: 1.0
 */
@Aspect
@Component
@Slf4j
public class SystemLogAspect {

    //"ThreadLocal beginTime"
    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
    //"ThreadLocal log"
    private static final ThreadLocal<Log> logThreadLocal =  new NamedThreadLocal<Log>("ThreadLocal log");
    //"ThreadLocal user"
    private static final ThreadLocal<User> currentUser=new NamedThreadLocal<>("ThreadLocal user");

    @Autowired(required=false)
    HttpServletRequest request;

    @Autowired
    LogService logService;

    /**
     * Service切层点
     */
    @Pointcut(value = "@annotation(comg.springboot.log.demo.annotation.SysServiceLog)")
    public void serviceAspect(){}

    /**
     * Controller 切层点
     */
    @Pointcut(value = "@annotation(comg.springboot.log.demo.annotation.SysControllerLog)")
    public void controllerAspect(){}

    /**
     * 前置通知
     * @param joinPoint
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint){
        log.info("进入日志切面前置通知!!");

        Date beginTime = new Date();
        //线程绑定变量（该数据只有当前请求的线程可见）
        beginTimeThreadLocal.set(beginTime);

        if (log.isDebugEnabled()){//这里日志级别为debug
            log.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                    .format(beginTime), request.getRequestURI());
        }

        //模拟数据库获取用户信息
//        User user = new User(1,"张三",18);
        final HttpSession session = request.getSession();
        User user = (User)session.getAttribute("loginUser");
        log.info("user=====:"+user);
        currentUser.set(user);

    }

    /**
     * 后置通知（不管是否发生异常）
     * @param joinPoint
     */
    @After("controllerAspect()")
    public void doAfter(JoinPoint joinPoint){

        log.info("====进入日志切面后置通知!!=====");
        User user = currentUser.get();
        if(!StringUtils.isEmpty(user)){
            String title="";
            String type="info";                       //日志类型(info:入库,error:错误)
            String remoteAddr=request.getRemoteAddr();//请求的IP
            String requestUri=request.getRequestURI();//请求的Uri
            String method=request.getMethod();        //请求的方法类型(post/get)
            Map<String,String[]> params=request.getParameterMap(); //请求提交的参数

            try {
                title=getControllerMethodDescription2(joinPoint);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 打印JVM信息。
            long beginTime = beginTimeThreadLocal.get().getTime();//得到线程绑定的局部变量（开始时间）
            long endTime = System.currentTimeMillis();  //2、结束时间
            if (log.isDebugEnabled()){
                log.debug("计时结束：{}  URI: {}  耗时： {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
                        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime),
                        request.getRequestURI(),
//                        DateUtils.formatDateTime(endTime - beginTime),
                        endTime - beginTime,
                        Runtime.getRuntime().maxMemory()/1024/1024,
                        Runtime.getRuntime().totalMemory()/1024/1024,
                        Runtime.getRuntime().freeMemory()/1024/1024,
                        (Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory())/1024/1024);
            }
            log.info("设置日志信息存储到表中!!");
            Log log=new Log();
//            log.setLogId(UUID.randomUUID().toString());
            log.setTitle(title);
//            log.setType(type);
//            log.setRemoteAddr(remoteAddr);
//            log.setRequestUri(requestUri);
//            log.setMethod(method);
            log.setMapToParams(params);
//            log.setException("无异常!");
            log.setUserId(user.getId().toString());
            //开始时间
            Date operateDate=beginTimeThreadLocal.get();
            log.setOperateDate(operateDate);
//            log.setTimeout(Utils.timeStamp2Date((endTime - beginTime),""));

            //1.直接执行保存操作
            logService.addLog(log);

            //2.优化:异步保存日志
            //new SaveLogThread(log, logService).start();

            //3.再优化:通过线程池来执行日志保存
//            threadPoolTaskExecutor.execute(new SaveLogThread(log, logService));
            logThreadLocal.set(log);

        }

    }

    /**
     * 最终通知(返回通知)（发生异常则不走）
     * @param res
     * @throws Throwable
     */
    @AfterReturning(returning = "res", pointcut = "controllerAspect()")
    public void doAfterReturning(Object res) throws Throwable {
        // 处理完请求，返回内容
        log.info("==========进入最终通知通知=========");
        log.info("返回接口响应参数:"+ JsonUtils.toJsonString(res));
        Log myLog = logThreadLocal.get();
        if (myLog!=null) {
            myLog.setResultParams(JsonUtils.toJsonString(res));
            log.info("==========更新日志参数=========");
//            new UpdateLogThread(log, logService).start();
            log.info(" 保存 doAfterReturning 日志");
        }
    }

    /**
     *  异常通知 记录操作报错日志
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.info("进入日志切面异常通知!!");
        log.info("异常信息:"+e.getMessage());
        Log mylog = logThreadLocal.get();
        if (mylog!=null) {
            mylog.setType("error");
            mylog.setException(e.toString());
//            new UpdateLogThread(log, logService).start();
            log.info(" 保存error 日志");
        }
    }

    /**
     * 保存日志线程
     */
    private static class SaveLogThread implements Runnable {
        private Log log;
        private LogService logService;

        public SaveLogThread(Log log, LogService logService) {
            this.log = log;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.addLog(log);
        }
    }

    /**
     * 日志更新线程
     */
    private static class UpdateLogThread extends Thread {
        private Log log;
        private LogService logService;

        public UpdateLogThread(Log log, LogService logService) {
            super(UpdateLogThread.class.getSimpleName());
            this.log = log;
            this.logService = logService;
        }

        @Override
        public void run() {
            this.logService.putLog(log);
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return discription
     */
    public static String getControllerMethodDescription2(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysControllerLog controllerLog = method.getAnnotation(SysControllerLog.class);
        String discription = controllerLog.desc();
        return discription;
    }


}
